namespace RL

open RL.Components
open RL.Drawing
open RL.Helpers
open RL.Mapping
open RL.Tiles
open Garnet.Composition
open Raylib_CsLo
open System.Collections.Generic

module FieldOfVision =
    let rec shadowcast(
        pointSet : List<Point2D>,
        map : RLMap,
        center : Point2D,
        range : int,
        distance : int,
        lowSlope : single,
        highSlope : single,
        octant : byte
    ) =
        if distance <= range then
            let low = floor((lowSlope * single distance) + 0.5F)
            let high = floor((highSlope * single distance) + 0.5F)

            let mutable lowSlope2 = lowSlope
            let mutable height = low
            let mutable inGap = false

            while height <= high do
                let x,y =
                    let tempD = if octant &&& 0x1uy > 0uy then -distance else distance
                    let tempH = if octant &&& 0x2uy > 0uy then -int(round height) else int(round height)
                    if octant &&& 0x4uy > 0uy then
                        (center.x + tempH, center.y + tempD)
                    else
                        (center.x + tempD, center.y + tempH)

                let inside = map.InBounds x y
                if inside && distance2D(center.x, x, center.y, y) < single range then
                    pointSet.Add({ x = x; y = y})
                if inside && (map.GetTile x y).tileType |> Obscures then
                    if inGap then
                        shadowcast(pointSet, map, center, range, distance + 1, lowSlope2, (height - 0.5F) / single distance, octant)
                    lowSlope2 <- (height + 0.5F) / single distance
                    inGap <- false
                else
                    inGap <- true
                    if height = high then
                        shadowcast(pointSet, map, center, range, distance + 1, lowSlope2, highSlope, octant)

                height <- height + 1F

    let calculateFOV(start : Point2D, range : int, map : RLMap) =
        let set = new List<Point2D>()
        set.Add(start)
        for oct in 1uy..8uy do shadowcast(set, map, start, range, 1, 0.0F, 1.0F, oct)
        Seq.toList set

    type FieldOfVision(range : int) =
        let mutable pointSet = []

        member val Dirty = true with get, set
        member val Range = range with get, set
        member _.PointSet = pointSet

        member f.Recalculate(start : Point2D, map : RLMap) =
            pointSet <- calculateFOV(start, f.Range, map)
//
// System Definitions
//
    type FOVProcess = struct end

    type Container with
        member c.RegisterFOVSystem =
            c.On<FOVProcess> <| fun _ ->
                let result, map : bool * RLMap = c.TryGetResource("Map")
                if result then
                    for e in c.Query<FieldOfVision, Point2D, Eid>() do
                        let fov = e.Value1
                        if fov.Dirty then
                            let pos = e.Value2
                            if c.Get(e.Value3).Has<Player>() then   // Check if this is the player.
                                let result, draw : bool * DrawCallContainer = c.TryGetResource("Draw")
                                if result then
                                    fov.Recalculate(pos, map)      // Recalculate the player's FOV.
                                    fov.Dirty <- false              // Clear the dirty flag.
                                    map.ClearVisible               // Clear the map's visibility array.
                                    for pt in fov.PointSet do
                                        map.SetVisible pt.x pt.y  // Set each point in the FOV set to visible on the map.
                                    map.RefreshExplored            // Repopulate the map explored array.
                            else
                                fov.Recalculate(pos, map)   // Recalculate the FOV.
                                fov.Dirty <- false           // Clear the dirty flag.

    let init(c : Container) =
        Disposable.Create[
            c.RegisterFOVSystem
        ] |> ignore
